<?php
/**
 * Created by PhpStorm
 * User: ZhuYunlong2018
 * Email: 920200256@qq.com
 * Date: 2021/4/22
 * Time: 12:48
 */

namespace app\admin\service;


use app\admin\annotation\Permission;
use app\admin\annotation\Validate;
use app\admin\cache\PermissionCache;
use app\lib\exception\DevException;
use Doctrine\Common\Annotations\Reader;
use ReflectionClass;
use ReflectionMethod;
use Symfony\Component\ClassLoader\ClassMapGenerator;
use think\App;
use think\event\RouteLoaded;
use think\facade\Env;


/**
 * 权限模块注解缓存
 * Trait InteractsWithPermission
 * @package app\admin\service
 * @property App $app
 * @property Reader $reader
 */
trait InteractsWithPermission
{

    /**
     * 注册注解路由
     */
    protected function registerAnnotationRoute()
    {
        $this->app->event->listen(RouteLoaded::class, function () {
            if ($this->app->getNamespace() !== "app\admin") {
                // 只注册admin模块的权限
                return;
            }
            if (Env::get('app_debug')) {
                //debug 模式，直接扫描读取
                $this->scanControllerDirs();
            } else {
                $cache = PermissionCache::instance()->getList();
                if (empty($cache)) {
                    //缓存不存在，扫描文件获取
                    $this->scanControllerDirs();
                }
            }
        });

    }

    /**
     * 扫描所有控制器获取对应类
     * @throws DevException
     * @throws \ReflectionException
     */
    public function scanControllerDirs() {
        $dirs = [$this->app->getAppPath() . $this->app->config->get('route.controller_layer')];
        foreach ($dirs as $dir) {
            if (is_dir($dir)) {
                $this->scanDir($dir);
            }
        }
    }

    /**
     * @param $dir
     * @throws DevException
     * @throws \ReflectionException
     */
    protected function scanDir($dir)
    {
        $permissionMap = [];
        foreach (ClassMapGenerator::createMap($dir) as $class => $path) {
            $refClass = new ReflectionClass($class);
            //方法
            foreach ($refClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $refMethod) {
                $permission = $this->getPermission($refMethod);
                if ($permission) {
                    $key = $this->getKey($refMethod);
                    $permissionMap[$key] = $permission;
                }
            }
        }
        // 保存到缓存中
        PermissionCache::instance()->saveList($permissionMap);
    }

    protected function getKey(ReflectionMethod $refMethod) {
        return $refMethod->class . "::" . $refMethod->name;
    }

    /**
     * 获取控制器方法的注解
     * @param ReflectionMethod $refMethod
     * @return Permission
     * @throws DevException
     */
    protected function getPermission(ReflectionMethod $refMethod) {
        //添加了权限的注解
        /** @var Permission $permission */
        $permission = $this->reader->getMethodAnnotation($refMethod, Permission::class);
        if ($permission) {
            $key = $this->getKey($refMethod);
            //添加了验证器的注解
            /** @var Validate $validate */
            $validate = $this->reader->getMethodAnnotation($refMethod, Validate::class);
            if ($validate) {
                // 校验注解
                $validate->check($key);
                $permission->validate = $validate;
            }
            // 校验注解是否正确
            $permission->check($key);
            $authKey = strstr($key, 'controller\\');
            $authKey = str_replace('controller\\', '', $authKey);
            // 如果key为app\\admin\\controller\\v1\\Test::index，则authKey为v.Test::index
            $permission->key = str_replace('\\', '.', $authKey);
        }
        return $permission;
    }

    /**
     * @param $class
     * @param $method
     * @return Permission|null
     * @throws DevException
     * @throws \ReflectionException
     */
    protected function getClassAnnotations($class, $method) {
        $permission = null;
        $refClass = new ReflectionClass($class);
        $refMethod = $refClass->getMethod($method);
        /** @var Permission $permission */
        $permission = $this->getPermission($refMethod);
        if ($permission) {
            $key = $this->getKey($refMethod);

            PermissionCache::instance()->addOne($key, $permission);
        }
        return $permission;
    }

    protected function getMethodAnnotations(ReflectionMethod $method, $annotationName)
    {
        $annotations = $this->reader->getMethodAnnotations($method);

        return array_filter($annotations, function ($annotation) use ($annotationName) {
            return $annotation instanceof $annotationName;
        });
    }

}